package ms.unicorn.dsl.runtime.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ms.unicorn.dsl.runtime.ExecutionContext;
import ms.unicorn.dsl.runtime.VariableStorage;
import ms.unicorn.dsl.unicornDSL.XExpression;
import ms.unicorn.dsl.unicornDSL.XFunctionDeclaration;
import ms.unicorn.dsl.utils.SphynxRuntimeException;
import ms.unicorn.dsl.utils.SphynxTypeMismatchException;

import org.eclipse.emf.ecore.EObject;

public class DefaultExecutionContext implements ExecutionContext {

	protected ExecutionContext parent, callerContext;
	protected Map<String, VariableStorage> variables = new HashMap<String, VariableStorage>();
	private XExpression evaluatingExpression;
	private XFunctionDeclaration executingFunc;
	private Object thisObject;

	public DefaultExecutionContext(ExecutionContext parentContext, ExecutionContext callerContext) {
		this.parent = parentContext;
		this.callerContext = callerContext;
		this.executingFunc = parentContext != null ? parentContext.getExecutingFunction() : null;
	}
	
	@Override
	public Object getThisObject() {
		return thisObject;
	}
	
	@Override
	public void setThisObject(Object value) {
		thisObject = value;
	}
	
	@Override
	public ExecutionContext getParent() {
		return this.parent;
	}
	
	@Override
	public ExecutionContext getCallerContext() {
		return callerContext;
	}

	@Override
	public VariableStorage allocateVariable(int type, boolean global, String qualifiedName, Object value) {
		VariableStorage var = new VariableImpl(type, global);
		variables.put(qualifiedName, var);
		var.setValue(value);
		return var;
	}

	@Override
	public VariableStorage getVariable(String qualifiedName,
			boolean allowCreateNew, boolean recursive) {
		VariableStorage var = variables.get(qualifiedName);
		if (var == null && recursive && parent != null)
			var = parent.getVariable(qualifiedName, allowCreateNew, recursive);
		return var;
	}

	@Override
	public XExpression getEvaluatingExpression() {
		return this.evaluatingExpression;
	}

	@Override
	public void setEvaluatingExpression(XExpression expression) {
		this.evaluatingExpression = expression;	
	}

	@Override
	public ExecutionContext getRootContext() {
		return parent != null ? parent.getRootContext() : this;
	}

	@Override
	public ExecutionContext createChildContext() {
		return new DefaultExecutionContext(this, getCallerContext());
	}

	@Override
	public ExecutionContext createCalleeContext() {
		return new DefaultExecutionContext(getParent(), this);
	}

	@Override
	public Collection<VariableStorage> getVariables(int varType) {
		if (varType == -1)
			return variables.values();
		List<VariableStorage> list = new ArrayList<VariableStorage>();
		for (VariableStorage var: variables.values())
			if ((var.getVariableType() & varType) > 0)
				list.add(var);
		return list;
	}

	@Override
	public Collection<String> getVariableNames(int varType) {
		if (varType == -1)
			return variables.keySet();
		List<String> list = new ArrayList<String>();
		for (String var: variables.keySet())
			if ((variables.get(var).getVariableType() & varType) > 0)
				list.add(var);
		return list;
	}

	@Override
	public <T> T getVariableValueWithDefault(String qualifiedName, T defaultValue) {
		VariableStorage var = getVariable(qualifiedName, false, false);  //variables.get(qualifiedName);
		return var != null ? (T)var.getValue() : defaultValue;
	}

	@Override
	public <T> T getVariableValue(String qualifiedName, Class<T> expected) {
		VariableStorage var = getVariable(qualifiedName, false, false);  // variables.get(qualifiedName);
		if (var == null)
			throw new SphynxRuntimeException(this, null, String.format("Argument named '%s' does not exists", qualifiedName));
		Object value = var.getValue();
		if (!expected.isInstance(value))
			throw new SphynxTypeMismatchException(this, null, expected, value);
		return (T) value;
	}

	@Override
	public XFunctionDeclaration getExecutingFunction() {
		return executingFunc;
	}

	@Override
	public void setExecutingFunction(XFunctionDeclaration func) {
		this.executingFunc = func;
		// update the parent context, if any
		if (callerContext != null) {			
			EObject container = func.eContainer();
			while (container != null && !(container instanceof XFunctionDeclaration))
				container = container.eContainer();
			if (container != null) {
				ExecutionContext cc = callerContext;
				while (cc != null && cc.getExecutingFunction() != container)
					cc = cc.getCallerContext();
				if (cc != null) 
					parent = cc;
			}
		}
	}

	@Override
	public String toString() {
		return (executingFunc != null ? executingFunc.getName() : "@root") + convertToString(getAllVariables());
	}
	
	protected Map<String, VariableStorage> getAllVariables() {
		return variables;
	}
	
	@SuppressWarnings("rawtypes")
	public String convertToString(Object obj) {
		if (obj instanceof List) {
			StringBuilder list = new StringBuilder();
			boolean first = true;
			list.append("[");
			for (Object o: (List)obj) {
				if (!first)
					list.append(", ");
				first = false;
				list.append(convertToString(o));
			}
			list.append("]");
			return list.toString();
		}
		else if (obj instanceof Map) {
			StringBuilder dump = new StringBuilder();
			dump.append('[');
			boolean first = true;
			for (Object varName: ((Map)obj).keySet()) {
				Object var = ((Map)obj).get(varName);
				if (!first)
					dump.append(", ");
				dump.append(varName);
				dump.append('=');
				dump.append(convertToString(var));
				first = false;
			}
			dump.append(']');
			return dump.toString();
		}
		return obj.toString();
	}

	@Override
	public VariableStorage addVariable(String qualifiedName,
			VariableStorage variable) {
		variables.put(qualifiedName, variable);
		return variable;
	}

	
	@Override
	public ExecutionContext createDependentContext() {
		ExecutionContext context = new DependentExecutionContext(this, callerContext);
		return context;
	}
	
	@Override
	public void clearVariables() {
		variables.clear();
	}
}
